﻿/****************
* Authored by Zhuo "Wizcas" Chen
* 2018 © Wubi.fun all rights reserved
*****************/

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEngine;
using Wubi.Utils;

public class Pattern
{
    public enum Kind
    {
        Unkown,
        Vertical,
        Row,
        Cross,
        Surround,
        COUNT
    }
    const string REGEX = @"^(\d+)([a-zA-Z]+)$";
    Dictionary<Kind, List<Combo>> _kind2Combos = new Dictionary<Kind, List<Combo>>();
    List<Combo> this[Kind kind]
    {
        get
        {
            List<Combo> cmbs;
            if (!_kind2Combos.TryGetValue(kind, out cmbs))
            {
                return null;
            }
            return cmbs;
        }
    }

    public override string ToString()
    {
        return _kind2Combos.Print("\n", kv =>
        {
            return string.Format("<{0}>\n- {1}", kv.Key, kv.Value.Print("\n- "));
        });
    }
    public IEnumerable<Combo> GetMatchingCombos(BlockStackMatrix matrix)
    {
        for (var kind = Pattern.Kind.Vertical; kind < Pattern.Kind.COUNT; kind++)
        {
            var cmbs = this[kind];
            if (cmbs == null) continue;
            foreach (var cmb in cmbs)
            {
                if (cmb.IsMatch(matrix)) yield return cmb;
            }
        }
    }
    public void ParseCombo(Kind kind, string value)
    {
        var parts = value.Split('=');
        if (parts.Length < 2) return;
        var combo = Combo.MakeCombo(kind);
        if (combo == null) return;
        combo.outcome = parts[1];
        string mixStr = parts[0];
        var mixVals = mixStr.Split(new[] { ';' }, System.StringSplitOptions.RemoveEmptyEntries);
        combo.mixes = new Mix[mixVals.Length];
        int i = 0;
        foreach (var mixv in mixVals)
        {
            var mix = new Mix();
            var setVals = mixv.Split('+');
            foreach (var setv in setVals)
            {
                var m = Regex.Match(setv, REGEX);
                if (m != null && m.Success && m.Groups.Count >= 3)
                {
                    var id = m.Groups[2].Value;
                    if (!string.IsNullOrEmpty(id))
                    {
                        var blockset = new BlockSet { id = id, count = int.Parse(m.Groups[1].Value) };
                        mix.AddSet(blockset);
                    }
                    else
                    {
                        Debug.LogErrorFormat("An empty id in combo formular: {0}", value);
                    }
                }
            }
            combo.mixes[i++] = mix;
        }
        AddCombo(kind, combo);
    }

    void AddCombo(Kind kind, Combo combo)
    {
        if (combo == null) return;
        List<Combo> cmbs = this[kind];
        if (cmbs == null)
        {
            cmbs = new List<Combo>();
            _kind2Combos[kind] = cmbs;
        }
        cmbs.Add(combo);
    }

}
public abstract class Combo
{
    public Mix[] mixes;
    public string outcome;
    public abstract Pattern.Kind kind { get; }

    public List<Block> matchedBlocks { get; private set; }

    public static Combo MakeCombo(Pattern.Kind kind)
    {
        switch (kind)
        {
            case Pattern.Kind.Vertical:
                return new VerticalCombo();
            case Pattern.Kind.Row:
                return new RowCombo();
            case Pattern.Kind.Cross:
                return new CrossCombo();
            case Pattern.Kind.Surround:
                return new CrossCombo();
            default:
                return null;
        }
    }

    public override string ToString()
    {
        return string.Format("{0}={1}", mixes.Print(";"), outcome);
    }
    public bool IsMatch(BlockStackMatrix matrix)
    {
        if (mixes != null && mixes.Length > 0)// { matchedBlocks = null; return false; }
        {
            matchedBlocks = new List<Block>();
            if (IsMatchInternally(matrix)) return true;
        }
        matchedBlocks = null;
        return false;
    }

    protected virtual bool IsMatchInternally(BlockStackMatrix matrix) { return false; }
}

public class VerticalCombo : Combo
{
    public override Pattern.Kind kind { get { return Pattern.Kind.Vertical; } }

    protected override bool IsMatchInternally(BlockStackMatrix matrix)
    {
        return mixes[0].IsMatch(matrix.o, matchedBlocks, false);
    }
}
public class RowCombo : Combo
{
    public override Pattern.Kind kind { get { return Pattern.Kind.Row; } }
    protected override bool IsMatchInternally(BlockStackMatrix matrix)
    {
        if (mixes.Length < 3) return false;
        var succ = mixes[0].IsMatch(matrix.o, matchedBlocks, true);
        if (!succ) return false;
        Mix mx1 = mixes[1], mx2 = mixes[2];
        var cross = matrix.cross;
        BlockStack[] we = cross[0], sn = cross[1];
        if (we.Any(s => BlockStack.IsEmpty(s)) && sn.Any(s => BlockStack.IsEmpty(s))) return false;

        var tmpBlockSet = new List<Block>();
        System.Action join = () => { matchedBlocks.AddRange(tmpBlockSet); };
        if (mx1.IsMatch(we[0], tmpBlockSet, true) && mx2.IsMatch(we[1], tmpBlockSet, true)) { join(); return true; } // west-o-east
        tmpBlockSet.Clear();
        if (mx1.IsMatch(we[1], tmpBlockSet, true) && mx2.IsMatch(we[0], tmpBlockSet, true)) { join(); return true; } // east-o-west
        tmpBlockSet.Clear();
        if (mx1.IsMatch(sn[0], tmpBlockSet, true) && mx2.IsMatch(sn[1], tmpBlockSet, true)) { join(); return true; } // south-o-north
        tmpBlockSet.Clear();
        if (mx1.IsMatch(sn[1], tmpBlockSet, true) && mx2.IsMatch(sn[0], tmpBlockSet, true)) { join(); return true; } // north-o-south
        return false;
    }


}
public class CrossCombo : Combo
{
    public override Pattern.Kind kind { get { return Pattern.Kind.Cross; } }
    protected override bool IsMatchInternally(BlockStackMatrix matrix)
    {
        if (mixes.Length < 5) return false;
        var succ = mixes[0].IsMatch(matrix.o, matchedBlocks, true);
        if (!succ) return false;
        Mix mxH1 = mixes[1], mxH2 = mixes[2], mxV1 = mixes[3], mxV2 = mixes[4];
        var cross = matrix.cross;
        BlockStack[] we = cross[0], sn = cross[1];
        if (we.Any(s => BlockStack.IsEmpty(s)) || sn.Any(s => BlockStack.IsEmpty(s))) return false;

        var tmpBlockSet = new List<Block>();
        System.Action join = () => { matchedBlocks.AddRange(tmpBlockSet); };
        var isWEok = true;
        if (mxV1.IsMatch(we[0], tmpBlockSet, true) && mxV2.IsMatch(we[1], tmpBlockSet, true)) { join(); isWEok = true; }
        if (!isWEok && mxH1.IsMatch(we[1], tmpBlockSet, true) && mxH2.IsMatch(we[0], tmpBlockSet, true)) { join(); isWEok = true; }
        if (!isWEok && mxV1.IsMatch(we[1], tmpBlockSet, true) && mxV2.IsMatch(we[0], tmpBlockSet, true)) { join(); isWEok = true; }
        if (!isWEok && mxH1.IsMatch(we[0], tmpBlockSet, true) && mxH2.IsMatch(we[1], tmpBlockSet, true)) { join(); isWEok = true; }
        if (isWEok)
        {
            if (mxH1.IsMatch(sn[0], tmpBlockSet, true) && mxH2.IsMatch(sn[1], tmpBlockSet, true)) { join(); return true; }
            if (mxV1.IsMatch(sn[0], tmpBlockSet, true) && mxV2.IsMatch(sn[1], tmpBlockSet, true)) { join(); return true; }
            if (mxH1.IsMatch(sn[1], tmpBlockSet, true) && mxH2.IsMatch(sn[0], tmpBlockSet, true)) { join(); return true; }
            if (mxV1.IsMatch(sn[1], tmpBlockSet, true) && mxV2.IsMatch(sn[0], tmpBlockSet, true)) { join(); return true; }
        }

        return false;
    }
}
public class SurroundCombo : Combo
{
    public override Pattern.Kind kind { get { return Pattern.Kind.Surround; } }
}

public class Mix
{
    public Dictionary<string, BlockSet> sets = new Dictionary<string, BlockSet>();
    public void AddSet(BlockSet set)
    {
        BlockSet existingSet;
        if (!sets.TryGetValue(set.id, out existingSet))
        {
            existingSet = set;
        }
        else
        {
            existingSet.count += set.count;
        }
        sets[set.id] = existingSet;
    }
    public override string ToString()
    {
        return sets.Values.Print("+");
    }

    public bool IsMatch(BlockStack stack, List<Block> matchedBlocks, bool mustFromBase) // returns the index of the first block in match sequence
    {
        if (stack == null) { return false; }
        for (int i = 0; i < stack.Count; i++)
        {
            if (stack.Count - i < flatten.Count) break; // not enough blocks left
            var ids = flatten.ToList();
            var matched = new List<Block>();
            for (int j = i; j < stack.Count; j++)
            {
                var test = stack[j];
                if (!ids.Contains(test.id)) break; // sequence corrupted
                ids.Remove(test.id);
                matched.Add(test);
                if (ids.Count == 0)
                {
                    matchedBlocks.AddRange(matched);
                    return true;
                }
            }
            if (mustFromBase) return false;
        }
        return false;
    }

    List<string> _fl;
    List<string> flatten
    {
        get
        {
            if (_fl == null)
            {
                _fl = new List<string>();
                foreach (var s in sets.Values)
                {
                    for (int i = 0; i < s.count; i++)
                    {
                        _fl.Add(s.id);
                    }
                }
            }
            return _fl;
        }
    }
}
public struct BlockSet
{
    public string id;
    public int count;
    public override string ToString()
    {
        return string.Format("{0}x{1}", id, count);
    }
}
